Skip to content

Add transfer type payload conversion hooks - #795

Open
tconley1428 wants to merge 3 commits into
mainfrom
data-model-payload-hooks
Open

Add transfer type payload conversion hooks#795
tconley1428 wants to merge 3 commits into
mainfrom
data-model-payload-hooks

Conversation

@tconley1428

@tconley1428 tconley1428 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Adds experimental SDK payload-converter support for values and target types marked with TemporalTransferTypeAttribute.

The attribute points at an ITemporalTransferTypeConverter implementation. The converter supplies the TransferType handed to the configured payload converter, converts user values into that representation for encoding, and reconstructs user values from it for decoding. General transfer type hooks intentionally do not receive a payload converter.

This keeps normal SDK payload-converter behavior in one place, including codec handling and serialization-context propagation.

This change:

  • wraps payload converters when they are stored on DataConverter, including with { PayloadConverter = ... }
  • adds experimental public TemporalTransferTypeAttribute and ITemporalTransferTypeConverter APIs
  • keeps the actual payload-converter adapter internal
  • caches validated transfer type converter instances per marked type
  • applies hooks before delegating to the configured payload converter
  • adds converter tests proving the transformed transfer type is encoded with the active workflow serialization context

@tconley1428
tconley1428 requested a review from a team as a code owner July 21, 2026 16:15
@tconley1428
tconley1428 marked this pull request as draft July 21, 2026 16:15
@tconley1428
tconley1428 force-pushed the data-model-payload-hooks branch from 2c28d33 to d1228be Compare July 21, 2026 18:07
@tconley1428 tconley1428 changed the title Add data model payload conversion hooks Add transfer type payload conversion hooks Jul 22, 2026
@tconley1428
tconley1428 marked this pull request as ready for review July 23, 2026 18:05
Comment thread src/Temporalio/Converters/TemporalTransferTypePayloadConverter.cs
Comment thread src/Temporalio/Converters/TemporalTransferTypePayloadConverter.cs
Comment thread src/Temporalio/Converters/TemporalTransferTypeAttribute.cs Outdated
Comment thread src/Temporalio/Converters/TemporalTransferTypeAttribute.cs Outdated
Comment thread src/Temporalio/Converters/TemporalTransferTypePayloadConverter.cs Outdated
Comment thread src/Temporalio/Converters/DataConverter.cs Outdated
Comment thread src/Temporalio/Converters/DataConverter.cs Outdated
@tconley1428
tconley1428 force-pushed the data-model-payload-hooks branch from 932f77e to ceb5be8 Compare July 27, 2026 16:48
}
}

options.DataConverter = TemporalTransferTypePayloadConverter.Wrap(options.DataConverter);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should clone the options and replace the data converter on the clone because if the caller looks at the data converter after constructing the client, they will see a different value on their options instance. We should probably make the clone before plugins operate on the options.

/// </summary>
/// <param name="connection">Connection for this client.</param>
/// <param name="options">Options for this client.</param>
public TemporalClient(ITemporalConnection connection, TemporalClientOptions options)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is looking better. One thing I'm thinking through is if the customer constructs another client based on the options that the first client has AND a plugin mutates the PayloadConverter, we'll have nested special internal payload converter within the chain and one as the very outermost instance. For example:

var client1 = new TemporalClient(connection, new TemporalClientOptions
{
    DataConverter = new DataConverter(inner, new DefaultFailureConverter()),
});

var client2 = new TemporalClient(connection, new TemporalClientOptions
{
    DataConverter = client1.Options.DataConverter,
    Plugins = new[] { plugin },
});

If plugin wraps the PayloadConverter during ConfigureClient, we end up with a PayloadConverter on client2 that looks like W(P(W(inner))) where W is the TemporalTransferTypePayloadConverter and P is the plugin payload converter. This is probably benign and not likely to happen all that often.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to completely fix this, might need to have an internal ResolvedDataConverter property on the options that is used internally throughout the library.

public NoJsonProtoPayloadConverter()
: base(
((DefaultPayloadConverter)DataConverter.Default.PayloadConverter).
new DefaultPayloadConverter().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably be reverted.

// Context-aware data converter and client
var dataConverter = new DataConverter(
PayloadConverter: new DefaultPayloadConverter(
((DefaultPayloadConverter)Client.Options.DataConverter.PayloadConverter).EncodingConverters.Select(e =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demonstrates part of the problem I wrote up in https://github.com/temporalio/sdk-dotnet/pull/795/changes#r3677825054. If we keep this behavior, maybe just use DataConverter.Default.PayloadConverter here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants